#include "CCDemo.h"

//----------------------------------------------------------------------------------------------------------------------------------
CCDemo::CCDemo(HINSTANCE hInstance): D3DApp(hInstance), 
	m_Camera(0),
	m_SysInput(0)
{
	// Seed random number generator.
	srand(static_cast<unsigned int>(time(0)));

	mEnable4xMsaa	= false;
	mMainWndCaption = L"Character Controller Demo";

	// Instantiate camera to immediately build projection matrix in OnResize.
	m_Camera = new Camera();
	m_Camera->Initialise();
}

//----------------------------------------------------------------------------------------------------------------------------------
CCDemo::~CCDemo()
{ }

//----------------------------------------------------------------------------------------------------------------------------------
bool CCDemo::Init()
{
	if (!D3DApp::Init())
		return false;

	Init_shaders();				// Compile and bind application shaders.
	Init_lights();				// Create application directional lights.

	//=================================================
	//	Instantiate Asset Manager
	//=================================================
	m_AssetManager		= new AssetManager();
	m_AssetManager		->Initialise(md3dDevice);

	//=================================================
	//	Initialise Input System
	//=================================================
	m_SysInput			= new SysInput;
	m_SysInput			->Initialise(m_Camera, AppInst(), MainWnd(), ScreenWidth(), ScreenHeight() );

	//=================================================
	//	Create the Player Movement System
	//=================================================
	m_PlayerInfo = new PlayerInfo();
	m_PlayerInfo->Init(md3dDevice, XMFLOAT3(0,0.5,0), XMFLOAT3(0,0,0), 
		m_AssetManager->CreatePlayerVertexBuffer(md3dDevice), 
		m_AssetManager->CreatePlayerIndexBuffer(md3dDevice),  
		m_AssetManager->GetMaterial("Box") );

	//=================================================
	//	Setup the Camera
	//=================================================
	m_Camera->SetCharacter(m_PlayerInfo->GetMesh());	// Set character for camera.
	m_Camera->UpdateView();
	
	return true;
}

//----------------------------------------------------------------------------------------------------------------------------------
void CCDemo::OnResize()
{
	D3DApp::OnResize();

	// Update the aspect ratio and recompute the projection matrix.
	m_Camera->SetLens(0.25f*XM_PI, AspectRatio(), 1.0f, 1000.0f);

	// Build the frustum from the projection matrix in view space.
	XNA::ComputeFrustumFromProjection(m_Camera->GetFrustum(), &m_Camera->Proj());
}

//----------------------------------------------------------------------------------------------------------------------------------
void CCDemo::UpdateScene(float dt)
{
	//=================================================
	//	Setup
	//=================================================
	bool  collision				= false;
	float desiredDistance		= 0.0f;		// Updated when we press right trigger. (for dollying out)

	vector<InstanceMap*>		instanceMaps;
	vector<ModelData*>			modelData;
	m_AssetManager				->GetInstanceMaps(instanceMaps);
	m_AssetManager				->GetModelData(modelData);
	
	//=================================================
	//	Input Updates
	//=================================================
	m_SysInput->Update(dt, &desiredDistance);
	m_SysInput->NormaliseVelocity();

	//=================================================
	//	Frustum Culling
	//=================================================
	SysFrustumCull::CullInstanceMaps(md3dImmediateContext, instanceMaps, modelData, m_Camera);

	//=================================================
	//	Camera Collision
	//=================================================
	CollisionUtil::CollisionUpdateCamera(md3dImmediateContext, m_SysInput->GetDollyOut(),
		instanceMaps, modelData, m_Camera, desiredDistance, dt);

	//=================================================
	//	Player Collision [Normalised inputVelocity]
	//=================================================
	m_PlayerInfo->CheckMovementCollision(&instanceMaps, &modelData, m_SysInput->GetVelocity(), dt);

	//=================================================
	//	Update View Matrix
	//=================================================
	m_Camera->UpdateView();
	
	//=================================================
	//	Update Title Bar Text
	//=================================================
	CollisionUtil::UpdateTitleBarStats(mMainWndCaption, instanceMaps);
}

//----------------------------------------------------------------------------------------------------------------------------------
void CCDemo::DrawScene()
{
	//=================================================
	//	Setup
	//=================================================
	vector<InstanceMap*>	instanceMaps;
	vector<ModelData*>		modelData;

	m_BasicShader			->SetDirLights(m_dirLights);
	m_BasicShader			->SetEyePosW(m_Camera->GetPosition() );

	m_InstancedShader		->SetDirLights(m_dirLights);
	m_InstancedShader		->SetEyePosW(m_Camera->GetPosition() );

	m_AssetManager			->GetInstanceMaps(instanceMaps);
	m_AssetManager			->GetModelData(modelData);

	XMMATRIX V				= m_Camera->View();
	XMMATRIX P				= m_Camera->Proj();
	XMMATRIX VP				= m_Camera->ViewProj();

	//=================================================
	//	Clear Back Buffer
	//=================================================
	md3dImmediateContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&Colors::LightSteelBlue));
	md3dImmediateContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);

	//=================================================
	//	Draw Mesh Geometry
	//=================================================
	std::vector<IMesh*>* basicMeshes = m_AssetManager->GetBasicMeshes();
	
	for (std::vector<IMesh*>::iterator it = basicMeshes->begin(); it != basicMeshes->end(); ++it)
		(*it)->Draw(md3dImmediateContext, V, P);
	
	//=================================================
	//	Draw Instances in Camera's Frustum
	//=================================================
	for (UINT i = 0; i < instanceMaps.size(); ++i)
	{
		SysFrustumCull::DrawInstances(md3dImmediateContext, instanceMaps[i], modelData[i], 
			m_Camera, m_AssetManager->GetMaterial("Grid"));
	}
	
	//=================================================
	//	Draw Player
	//=================================================
	m_PlayerInfo->GetMesh()->Draw(md3dImmediateContext, V, P);

	HR(mSwapChain->Present(0, 0));
}

//----------------------------------------------------------------------------------------------------------------------------------
void CCDemo::Shutdown()
{
	m_BasicShader		->Shutdown();
	m_InstancedShader	->Shutdown();

	SafeDelete(m_Camera)
	SafeDelete(m_SysInput);
	SafeDelete(m_AssetManager);

	D3DApp::Shutdown();
}

//----------------------------------------------------------------------------------------------------------------------------------
void CCDemo::Init_lights()
{
	// Initialise directional lights.
	m_dirLights[0].Ambient   = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
	m_dirLights[0].Diffuse   = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
	m_dirLights[0].Specular  = XMFLOAT4(0.6f, 0.6f, 0.6f, 16.0f);
	m_dirLights[0].Direction = XMFLOAT3(0.707f, -0.707f, 0.0f);

	m_dirLights[1].Ambient   = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
	m_dirLights[1].Diffuse   = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
	m_dirLights[1].Specular  = XMFLOAT4(0.3f, 0.3f, 0.3f, 16.0f);
	m_dirLights[1].Direction = XMFLOAT3(-0.707f, 0.0f, 0.707f);

	m_dirLights[2].Ambient	  = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
	m_dirLights[2].Diffuse	  = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
	m_dirLights[2].Specular  = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
	m_dirLights[2].Direction = XMFLOAT3(0.0f, -0.707f, -0.707f);
}

//----------------------------------------------------------------------------------------------------------------------------------
void CCDemo::Init_shaders()
{
	// Instantiate shaders.
	m_BasicShader = BasicShader::GetInstance();
	m_BasicShader->Initialise(md3dDevice);
	
	m_InstancedShader = InstancedShader::GetInstance();
	m_InstancedShader->Initialise(md3dDevice);
}